

import path from 'path';
import schema from 'async-validator';
import crypto from 'crypto';
import config from 'config';
import fs from 'fs';
import { AppContext } from 'ROOT/interface/App';
import KLineHistoryService from 'ROOT/module/kline-history/KLineHistory.service';
import SymbolFactorService from './SymbolFactor.service'
import { PERIOD_INTERVAL_MAP, runWorker, transformCSVToJSON, transformToCSV } from 'ROOT/utils';
import { outLogger } from 'ROOT/common/logger';
import { SymbolFactor, Statistics, sample } from 'ROOT/trader/Statistics';

import { Period, PeriodProcess } from 'ROOT/trader/PeriodProcess';
import { KlineMark, KlineWithVector } from 'ROOT/trader/FindTradingPoints';
import { findTradingPoints } from './tradingPoints.worker';
import { Cache } from 'ROOT/common/Cache';
import { toVector } from './vector.worker';
import { CustomIndicators } from 'ROOT/trader';


const periodCache = new Cache<KlineWithVector[]>(1000 * 60 * 25)
const pointPeriodCache = new Cache<Omit<KlineWithVector, 'time'>[]>(1000 * 60 * 25)
const publicPath = config.get('publicPath') as string;
const vectorDir = path.join(publicPath, 'vectors');
fs.mkdirSync(vectorDir, { recursive: true })
export default class SymbolFactorController {
  public static getTradingPoints = async (ctx: AppContext) => {
    const { start, end, symbol, period, target_profit, old } = ctx.request.query;
    const validator = new schema({
      start: {
        type: "string",
        required: true,
      },
      end: {
        type: "string",
        required: true,
      },
      symbol: {
        type: "string",
        required: true,
      },
      period: {
        type: 'string'
      },
      target_profit: {
        type: 'string'
      },
      old: {
        type: 'number'
      }
    });
    try {
      await validator.validate({ start, end, symbol, period, target_profit, old: Number(old) });
    } catch ({ errors, fields }) {
      ctx.sendError({ errors });
      return;
    }

    try {
      const history = await KLineHistoryService.find({
        start: Number(start),
        end: Number(end),
        symbol: (symbol as string).toLowerCase(),
      });

      const { periodHistory, tradingPoints, periodList_15m } = await runWorker<typeof findTradingPoints>(path.resolve(__dirname, './tradingPoints.worker.ts'), {
        period: period as Period,
        symbol: symbol as string,
        target_profit: Number(target_profit),
      }, {
        workerData: {
          history
        }
      })

      const marks: KlineMark[] = [];

      const cacheTag = crypto.createHash('sha256').digest('hex');

      periodCache.set(cacheTag, periodList_15m);

      tradingPoints.forEach(item => {
        marks.push({
          symbol: symbol as string,
          timestamp: item.timestamp,
          close: item.close,
          label_text: item.label == -1 ? '做空' : '做多',
          label: item.label,
        });
      });

      pointPeriodCache.set(symbol as string, tradingPoints);

      const periodMarks: KlineMark[] = period === '15m' ? marks : [];

      if (period != '15m') {
        const periodProcess = new PeriodProcess<KlineMark>([period as Period], 0);

        periodProcess.onPeriod((period, data) => {
          if (data) {
            periodMarks.push(data);
          }
        });

        marks.forEach(data => {
          if (!data.timestamp) {
            return
          }
          periodProcess.process(data.timestamp, (mergeData) => {
            // console.log(new Date(data.timestamp).toLocaleString(), data)
            mergeData.symbol = data.symbol;
            mergeData.close = Number(data.close);
            mergeData.timestamp = data.timestamp;
            if (mergeData.label === undefined) {
              mergeData.label = data.label;
            }
          });
        });
      }

      if (Number(old)) {
        // 修改老数据
        const symbolFactorHistory = await SymbolFactorService.find({
          symbol: (symbol as string).toLowerCase(),
          period: period as Period,
        });

        outLogger.info(`symbolFactorHistory.length${symbolFactorHistory.length}  tradingPoints:${tradingPoints.length}`);

        if (tradingPoints.length > 0) {
          // 删除旧的
          await SymbolFactorService.deleteMany(symbol as string, period as Period);
          sample.symbolVectors[`${symbol}-${period}`] = []
        }
        // 写入特征数据
        for (let i = 0; i < tradingPoints.length; i++) {
          const element = tradingPoints[i];
          const { feature_vector_detail, label } = element;
          const target = tradingPoints.find(item => item.timestamp === element.timestamp)
          if (target) {
            if (feature_vector_detail[`vector_stort_${period}`]) {
              sample.symbolVectors[`${symbol}-${period}`].push({
                vectors: feature_vector_detail[`vector_stort_${period}`],
                label: label,
                time: new Date(element.timestamp)
              })
            }
            await SymbolFactorService.create({
              symbol: symbol as string,
              feature_vector: period == '4h' ? Statistics.jsonToVectors(
                feature_vector_detail
              ) : feature_vector_detail[`vector_stort_${period}`],
              label: label,
              time: new Date(element.timestamp),
              period: period as string,
            });
          }
        }
      }
      ctx.sendSuccess({
        data: {
          history: periodHistory,
          marks: transformToCSV(periodMarks),
          tag: cacheTag
        }
      });
    } catch (error) {
      console.log(error)
      ctx.sendError({ message: error.message });
    }

  }

  /**
   * 特征重写
   */
  public static rewriteVector = async (ctx: AppContext) => {
    const { symbol, changeMarks, tag, period } = ctx.request.body as { symbol: string, changeMarks: (KlineMark & { period?: string; })[], tag: string, period: Period };

    const validator = new schema({
      symbol: {
        type: "string",
        required: true,
      },
      changeMarks: {
        type: 'array',
      },
      tag: {
        type: 'string',
      }
    });

    try {
      await validator.validate({ symbol });
    } catch ({ errors, fields }) {
      ctx.sendError({ errors });
      return;
    }

    if (!periodCache.get(tag) || !pointPeriodCache.get(symbol)) {
      ctx.sendError({ message: '计算结果缓存已失效' });
      return;
    }
    try {
      // 新增从缓存拿feature_vector_detail结果
      const periodKline_15m = periodCache.get(tag);
      const pointPeriodList = pointPeriodCache.get(symbol);
      // 先看看有没有改动
      for (let i = 0; i < changeMarks.length; i++) {
        const element = changeMarks[i];
        const timeRang = [element.timestamp - PERIOD_INTERVAL_MAP[element.period], element.timestamp + PERIOD_INTERVAL_MAP[element.period]];
        const fliters = periodKline_15m.filter(item => {
          return item.timestamp >= timeRang[0] && item.timestamp <= timeRang[1];
        });
        if (fliters.length === 0) {
          outLogger.info('fliters empty')
          continue;
        }
        if (element.label === 1) {
          const minKline = fliters.reduce((min, item) => {
            if (item.close < min.close) {
              return item
            }
            return min
          }, fliters[0]);
          const oldIndex = pointPeriodList.findIndex(item => item.timestamp == minKline.timestamp);
          if (oldIndex === -1) {
            pointPeriodList.push(minKline)
          }
        } else if (element.label === -1) {
          const maxKline = fliters.reduce((max, item) => {
            if (item.close > max.close) {
              return item
            }
            return max
          }, fliters[0]);
          const oldIndex = pointPeriodList.findIndex(item => item.timestamp == maxKline.timestamp)

          if (oldIndex === -1) {
            pointPeriodList.push(maxKline)
          }
        } else if (element.label === 0) {
          // 删除动作
          const index = pointPeriodList.findIndex(v => v.timestamp === element.timestamp);
          if (index > -1) {
            pointPeriodList.splice(index, 1);
          }
        }
      }
      // 更新缓存
      pointPeriodCache.set(symbol, pointPeriodList);
      // 删除旧的
      await SymbolFactorService.deleteMany(symbol as string, period);

      //  清空数据
      sample.clearVectors(symbol, period);
      console.log(pointPeriodList[10])
      // 写入特征数据
      for (let i = 0; i < pointPeriodList.length; i++) {
        const element = pointPeriodList[i];
        const { feature_vector_detail, label } = element;
        if (label === 0) {
          continue
        }

        if (feature_vector_detail[`vector_stort_${period}`]) {
          sample.symbolVectors[`${symbol}-${period}`].push({
            vectors: feature_vector_detail[`vector_stort_${period}`],
            label: label,
            time: new Date(element.timestamp)
          })
        }

        await SymbolFactorService.create({
          symbol: symbol,
          feature_vector: period == '4h' ? Statistics.jsonToVectors(
            feature_vector_detail
          ) : feature_vector_detail[`vector_stort_${period}`],
          label: label,
          time: new Date(element.timestamp),
          period: period
        });
      }
      ctx.sendSuccess({ data: pointPeriodList.length, message: '更新成功' });
    } catch (error) {
      console.log(error)
      ctx.sendError({ message: error.message });
      return;
    }

  }

  public static statisticsVector = async (ctx: AppContext) => {
    const { symbol, changeMarks, tag } = ctx.request.body as { symbol: string, changeMarks: (KlineMark & { period?: string; })[], tag: string };

    const validator = new schema({
      symbol: {
        type: "string",
        required: true,
      },
      changeMarks: {
        type: 'array',
      },
      tag: {
        type: 'string',
      }
    });

    try {
      await validator.validate({ symbol, changeMarks, tag });
    } catch ({ errors, fields }) {
      ctx.sendError({ errors });
      return;
    }

    if (!periodCache.get(tag) || !pointPeriodCache.get(symbol)) {
      ctx.sendError({ message: '计算结果缓存已失效' });
      return;
    }
    try {
      // 新增从缓存拿feature_vector_detail结果
      const periodKline_15m = periodCache.get(tag);
      const pointPeriodList = pointPeriodCache.get(symbol);
      // 先看看有没有改动
      for (let i = 0; i < changeMarks.length; i++) {
        const element = changeMarks[i];
        const timeRang = [element.timestamp - PERIOD_INTERVAL_MAP[element.period], element.timestamp + PERIOD_INTERVAL_MAP[element.period]];
        const fliters = periodKline_15m.filter(item => {
          return item.timestamp >= timeRang[0] && item.timestamp <= timeRang[1];
        });
        if (fliters.length === 0) {
          outLogger.info('fliters empty')
          continue;
        }
        if (element.label === 1) {
          const minKline = fliters.reduce((min, item) => {
            if (item.close < min.close) {
              return item
            }
            return min
          }, fliters[0]);
          const oldIndex = pointPeriodList.findIndex(item => item.timestamp == minKline.timestamp);
          if (oldIndex === -1) {
            pointPeriodList.push(minKline)
          }
        } else if (element.label === -1) {
          const maxKline = fliters.reduce((max, item) => {
            if (item.close > max.close) {
              return item
            }
            return max
          }, fliters[0]);
          const oldIndex = pointPeriodList.findIndex(item => item.timestamp == maxKline.timestamp)
          if (oldIndex === -1) {
            pointPeriodList.push(maxKline)
          }
        } else if (element.label === 0) {
          // 删除动作
          fliters.forEach(item => {
            const index = pointPeriodList.findIndex(v => v.timestamp === item.timestamp);
            if (index > -1) {
              pointPeriodList.splice(index, 1);
            }
          })
        }
      }
      const config = {
        'sell': {
          close_ma_rate: {
            max: 0,
            min: 3,
          },
          volume_rate: {
            max: 0,
            min: 3,
          },
          exceptional_undulation_rate: {
            max: 0,
            min: 3,
          }
        },
        'buy': {
          close_ma_rate: {
            max: 0,
            min: -3,
          },
          volume_rate: {
            max: 0,
            min: 3,
          },
          exceptional_undulation_rate: {
            max: 0,
            min: 3,
          }
        }
      }
      // 写入特征数据
      for (let i = 0; i < pointPeriodList.length; i++) {
        const element = pointPeriodList[i];
        const { feature_vector_detail, label } = element;
        if (feature_vector_detail.exceptional_undulation_rate_4h === 0) {
          continue;
        }
        const symbolConfig = config[label === -1 ? 'sell' : 'buy']
        symbolConfig.close_ma_rate.max = Math.max(symbolConfig.close_ma_rate.max, feature_vector_detail.close_ma_rate_15m);
        symbolConfig.close_ma_rate.min = Math.min(symbolConfig.close_ma_rate.min, feature_vector_detail.close_ma_rate_15m);
        symbolConfig.volume_rate.max = Math.max(symbolConfig.volume_rate.max, feature_vector_detail.volume_rate_15m);
        symbolConfig.volume_rate.min = Math.min(symbolConfig.volume_rate.min, feature_vector_detail.volume_rate_15m);
        symbolConfig.exceptional_undulation_rate.max = Math.max(symbolConfig.exceptional_undulation_rate.max, feature_vector_detail.exceptional_undulation_rate_15m);
        symbolConfig.exceptional_undulation_rate.min = Math.min(symbolConfig.exceptional_undulation_rate.min, feature_vector_detail.exceptional_undulation_rate_15m);
      }
      const logic = `
      function strategy(kline, indicators, mixIndicator) {
        const {
          close_ma_rate_15m,
          close_ma_rate_4h,
          close_ma_rate_1d,
          slope_15m,
          volume_rate_15m,
          point_status_15m,
          exceptional_undulation_rate_15m,
          exceptional_undulation_rate_4h
        } = mixIndicator;
        const positive = kline.close > kline.open;
        if (
          close_ma_rate_15m > ${config.sell.close_ma_rate.min} &&
          volume_rate_15m > ${config.sell.volume_rate.min} &&
          exceptional_undulation_rate_15m > ${config.sell.exceptional_undulation_rate.min} &&
          slope_15m > 0 &&
          (point_status_15m === -1 ||
            close_ma_rate_4h > 0.3 ||
            exceptional_undulation_rate_15m > 0.4) &&
          positive
        ) {
          return "SELL";
        } else if (
          close_ma_rate_15m < ${config.buy.close_ma_rate.min} &&
          volume_rate_15m > ${config.buy.volume_rate.min} &&
          exceptional_undulation_rate_15m > ${config.buy.exceptional_undulation_rate.min} &&
          slope_15m < 0 &&
          (point_status_15m === 1 ||
            close_ma_rate_4h < -0.3 ||
            exceptional_undulation_rate_15m > 0.4) &&
          !positive
        ) {
          return "BUY";
        }
      }
      var config = {
        mode: 1,
      };
      `;
      // console.log(config)
      ctx.sendSuccess({ data: logic, message: '返回成功' });
    } catch (error) {
      ctx.sendError({ message: error.message });
      return;
    }

  }
  /**
   * 价格向量化
   */
  public static priceToVector = async (ctx: AppContext) => {
    const { symbol, start, end } = ctx.request.body as { symbol: string, start: string, end: string };

    const validator = new schema({
      symbol: {
        type: "string",
        required: true,
      },
      start: {
        type: "string",
        required: true,
      },
      end: {
        type: "string",
        required: true,
      },
    });

    try {
      await validator.validate({ symbol, start, end });
    } catch ({ errors, fields }) {
      ctx.sendError({ errors });
      return;
    }

    try {
      const history = await KLineHistoryService.find({
        start: new Date(start),
        end: new Date(end),
        symbol: (symbol).toLowerCase(),
      });
      const vectors_4h = await runWorker<typeof toVector>(path.resolve(__dirname, './vector.worker.ts'), { period: '4h' }, {
        workerData: {
          history,
          symbol
        }
      })
      const vectors_1d = await runWorker<typeof toVector>(path.resolve(__dirname, './vector.worker.ts'), { period: '1d' }, {
        workerData: {
          history,
          symbol
        }
      })
      // 先看看有没有改动
      fs.writeFileSync(path.resolve(vectorDir, `${symbol}_vectors_4h.csv`), vectors_4h);
      fs.writeFileSync(path.resolve(vectorDir, `${symbol}_vectors_1d.csv`), vectors_1d);

      ctx.sendSuccess({ data: {}, message: '更新成功' });
    } catch (error) {
      ctx.sendError({ message: error.message });
      return;
    }

  }

}

